using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;

// Tranquillity
using Tranquillity;

namespace TranquillityDemos
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Demos : Microsoft.Xna.Framework.Game
    {
        #region Fields


        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        ParticleManager particleManager;

        string[] effects =
        {
            "Explosions",
            "Smoke Plume",
            "Ring of fire"
        };

        int currentState = 0;

        Matrix view;
        Matrix projection;

        Model grid;

        SpriteFont arial;

        Texture2D explosion;
        Texture2D fire;
        Texture2D smoke;

        // Camera state.
        float cameraArc = -5;
        float cameraRotation = 0;
        float cameraDistance = 300;

        float? initialPinchDistance;

        FireParticleSystem fireParticleSystem;
        SmokeParticleSystem smokePlumeParticleSystem;

        SmokeRingEmitter smokeRingEmitter;
        SmokePlumeEmitter smokePlumeEmitter;

        ExplosionParticleSystem explosionParticleSystem;
        ExplosionSmokeParticleSystem explosionSmokeParticleSystem;
        ProjectileTrailParticleSystem projectileTrailParticleSystem;

        List<Projectile> projectiles = new List<Projectile>();

        TimeSpan timeToNextProjectile = TimeSpan.Zero;

        const int smokePlumeParticles = 50;
        const int smokeRingParticles = 50;
        const int fireRingSystemParticles = 250;


        #endregion

        #region Initialization


        public Demos()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            // Frame rate is 30 fps by default for Windows Phone.
            TargetElapsedTime = TimeSpan.FromTicks(333333);

            graphics.PreferredBackBufferWidth = 800;
            graphics.PreferredBackBufferHeight = 480;
            graphics.IsFullScreen = true;

            IsFixedTimeStep = false;

            particleManager = new ParticleManager(this);
            Components.Add(particleManager);

            TouchPanel.EnabledGestures = GestureType.Tap | GestureType.FreeDrag | GestureType.Pinch | GestureType.PinchComplete;
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            arial = Content.Load<SpriteFont>("Fonts/Font");

            grid = Content.Load<Model>("Models/grid");

            explosion = Content.Load<Texture2D>("Textures/explosion");
            fire = Content.Load<Texture2D>("Textures/fire");
            smoke = Content.Load<Texture2D>("Textures/smoke");

            // Ring
            fireParticleSystem = new FireParticleSystem(500, fire);
            smokePlumeParticleSystem = new SmokeParticleSystem(500, smoke);

            smokePlumeEmitter = new SmokePlumeEmitter(Vector3.Zero, 0);
            smokeRingEmitter = new SmokeRingEmitter(Vector3.Zero, 0);

            smokePlumeParticleSystem.AddEmitter(smokePlumeEmitter);
            smokePlumeParticleSystem.AddEmitter(smokeRingEmitter);

            particleManager.AddParticleSystem(smokePlumeParticleSystem, BlendState.NonPremultiplied);
            particleManager.AddParticleSystem(fireParticleSystem, BlendState.Additive);

            // Explosions
            explosionParticleSystem = new ExplosionParticleSystem(100, explosion);
            explosionParticleSystem.AddAffector(new VelocityAffector(Vector3.Down));

            explosionSmokeParticleSystem = new ExplosionSmokeParticleSystem(100, smoke);
            explosionSmokeParticleSystem.AddAffector(new VelocityAffector(Vector3.Down));

            projectileTrailParticleSystem = new ProjectileTrailParticleSystem(500, smoke);

            particleManager.AddParticleSystem(explosionSmokeParticleSystem, BlendState.NonPremultiplied);
            particleManager.AddParticleSystem(projectileTrailParticleSystem, BlendState.NonPremultiplied);
            particleManager.AddParticleSystem(explosionParticleSystem, BlendState.Additive);
        }


        #endregion

        #region Update


        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            ProcessInput(gameTime);

            if (currentState == 0)
            {
                UpdateExplosions(gameTime);
            }

            UpdateProjectiles(gameTime);

            particleManager.SetMatrices(view, projection);

            base.Update(gameTime);
        }


        #endregion

        #region Draw


        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // Compute camera matrices.
            float aspectRatio = (float)GraphicsDevice.Viewport.Width /
                                (float)GraphicsDevice.Viewport.Height;

            view = Matrix.CreateTranslation(0, -25, 0) *
                Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) *
                Matrix.CreateRotationX(MathHelper.ToRadians(cameraArc)) *
                Matrix.CreateLookAt(new Vector3(0, 0, -cameraDistance),
                                    new Vector3(0, 0, 0), Vector3.Up);

            projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                                                                    aspectRatio,
                                                                    1, 10000);

            DrawGrid(view, projection);

            DrawMessage();

            base.Draw(gameTime);
        }


        #endregion

        #region Private Methods


        /// <summary>
        /// Helper for updating the explosions effect.
        /// </summary>
        void UpdateExplosions(GameTime gameTime)
        {
            timeToNextProjectile -= gameTime.ElapsedGameTime;

            if (timeToNextProjectile <= TimeSpan.Zero)
            {
                // Create a new projectile once per second. The real work of moving
                // and creating particles is handled inside the Projectile class.
                projectiles.Add(new Projectile(explosionParticleSystem, explosionSmokeParticleSystem, projectileTrailParticleSystem));

                timeToNextProjectile += TimeSpan.FromSeconds(1);
            }
        }

        /// <summary>
        /// Helper for updating the list of active projectiles.
        /// </summary>
        void UpdateProjectiles(GameTime gameTime)
        {
            int i = 0;

            while (i < projectiles.Count)
            {
                if (!projectiles[i].Update(gameTime))
                {
                    projectileTrailParticleSystem.RemoveEmitter(projectiles[i].TrailEmitter);

                    // Remove projectiles at the end of their life.
                    projectiles.RemoveAt(i);
                }
                else
                {
                    // Advance to the next projectile.
                    i++;
                }
            }
        }

        void ProcessInput(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            float time = (float)gameTime.ElapsedGameTime.TotalMilliseconds;

            while (TouchPanel.IsGestureAvailable)
            {
                GestureSample gesture = TouchPanel.ReadGesture();

                switch (gesture.GestureType)
                {
                    case GestureType.Tap:
                        currentState++;

                        if (currentState > effects.Length - 1)
                            currentState = 0;

                        fireParticleSystem.EmissionRate = 0;
                        smokeRingEmitter.EmissionRate = 0;
                        smokePlumeEmitter.EmissionRate = 0;

                        switch (currentState)
                        {
                            case 1:
                                smokePlumeEmitter.EmissionRate = smokePlumeParticles;

                                break;

                            case 2:
                                fireParticleSystem.EmissionRate = fireRingSystemParticles;
                                smokeRingEmitter.EmissionRate = smokeRingParticles;

                                break;
                        }

                        break;

                    case GestureType.FreeDrag:
                        cameraArc -= gesture.Delta.Y * time * 0.01f;
                        cameraRotation += gesture.Delta.X * time * 0.01f;

                        // Limit the arc movement.
                        if (cameraArc > 90.0f)
                            cameraArc = 90.0f;
                        else if (cameraArc < -90.0f)
                            cameraArc = -90.0f;

                        break;

                    case GestureType.Pinch:
                        float currentPinchDistance = Vector2.Distance(gesture.Position, gesture.Position2) * 0.0025f;

                        if (!initialPinchDistance.HasValue)
                        {
                            initialPinchDistance = currentPinchDistance;
                        }
                        else
                        {
                            cameraDistance -= (currentPinchDistance < initialPinchDistance ? -currentPinchDistance : currentPinchDistance) * time * 0.1f;

                            // Limit the camera distance.
                            if (cameraDistance > 500)
                            {
                                cameraDistance = 500;
                            }
                            else if (cameraDistance < 200)
                            {
                                cameraDistance = 200;
                            }
                        }

                        break;

                    case GestureType.PinchComplete:
                        initialPinchDistance = null;

                        break;
                }
            }
        }

        /// <summary>
        /// Helper for drawing the background grid model.
        /// </summary>
        void DrawGrid(Matrix view, Matrix projection)
        {
            GraphicsDevice device = graphics.GraphicsDevice;

            device.BlendState = BlendState.Opaque;
            device.DepthStencilState = DepthStencilState.Default;
            device.SamplerStates[0] = SamplerState.LinearWrap;

            grid.Draw(Matrix.Identity, view, projection);
        }

        /// <summary>
        /// Helper for drawing our message text.
        /// </summary>
        void DrawMessage()
        {
            spriteBatch.Begin();
            spriteBatch.DrawString(arial, "Current effect: ", new Vector2(50, 50), Color.White);
            spriteBatch.DrawString(arial, effects[(int)currentState], new Vector2(220, 50), Color.White);
            spriteBatch.DrawString(arial, "Tap to switch demo", new Vector2(50, 70), Color.White);
            spriteBatch.End();
        }


        #endregion
    }
}
